package java虚拟机.chapter7;

import static java.lang.invoke.MethodHandles.Lookup;
import static java.lang.invoke.MethodType.methodType;

import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.invoke.VolatileCallSite;

/**
 * @author juntao zhang
 */
public class CallSiteTest {

  static class ClassA {

    public void println(String s) {
      System.out.println("classA=>" + s);
    }
  }

  public static void main(String[] args) throws Throwable {
//    myTest();
    useConstantCallSite();
//    useMutableCallSite();
//    testMutableCallSite();
//    testVolatileCallSite();
  }

  private static void myTest() throws Throwable {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    Object obj = System.currentTimeMillis() % 2 == 0 ? System.out : new ClassA();
    MethodType mt = methodType(void.class, String.class);
    MethodHandle mh = lookup.findVirtual(obj.getClass(), "println", mt).bindTo(obj);
    mh.invokeExact("juntao");
  }

  private static void useConstantCallSite() throws Throwable {
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodType type = methodType(String.class, int.class, int.class);
    MethodHandle mh = lookup.findVirtual(String.class, "substring", type);
    String result = (String) mh.invoke("Hello", 2, 4);
    System.out.println(result);

    ConstantCallSite callSite = new ConstantCallSite(mh);
    MethodHandle invoker = callSite.dynamicInvoker();

    String result2 = (String) invoker.invoke("Hello", 2, 4);
    System.out.println(result2);
  }

  public static void useMutableCallSite() throws Throwable {
    MethodType type = methodType(int.class, int.class, int.class);
    MutableCallSite callSite = new MutableCallSite(type);
    MethodHandle invoker = callSite.dynamicInvoker();
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle mhMax = lookup.findStatic(Math.class, "max", type);
    MethodHandle mhMin = lookup.findStatic(Math.class, "min", type);
    callSite.setTarget(mhMax);
    int result = (int) invoker.invoke(3, 5); //值为5
    System.out.println(result);
    callSite.setTarget(mhMin);
    result = (int) invoker.invoke(3, 5); //值为3
    System.out.println(result);
  }


  private final static Class<?> CLASS = CallSiteTest.class;

  private static CallSite mcs;
  private static CallSite vcs;
  private static MethodHandle mh_foo;
  private static MethodHandle mh_bar;

  static {
    try {
      mh_foo = MethodHandles.lookup()
          .findStatic(CLASS, "foo", methodType(int.class, int.class, int.class));
      mh_bar = MethodHandles.lookup()
          .findStatic(CLASS, "bar", methodType(int.class, int.class, int.class));
      mcs = new MutableCallSite(mh_foo);
      vcs = new VolatileCallSite(mh_foo);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private final static int N = Integer.MAX_VALUE / 100;
  private final static int RESULT1 = 762786192;
  private final static int RESULT2 = -21474836;

  private static void assertEquals(int expected, int actual) {
    if (expected != actual) {
      throw new AssertionError("expected: " + expected + ", actual: " + actual);
    }
  }

  private static void testMutableCallSite() throws Throwable {
    // warm-up
    for (int i = 0; i < 20000; i++) {
      mcs.setTarget(mh_foo);
    }
    // run
    for (int n = 0; n < 2; n++) {
      mcs.setTarget(mh_foo);
      for (int i = 0; i < 5; i++) {
        assertEquals(RESULT1, runMutableCallSite());
      }
      mcs.setTarget(mh_bar);
      for (int i = 0; i < 5; i++) {
        assertEquals(RESULT2, runMutableCallSite());
      }
    }
  }

  private static void testVolatileCallSite() throws Throwable {
    // warm-up
    for (int i = 0; i < 20000; i++) {
      vcs.setTarget(mh_foo);
    }
    // run
    for (int n = 0; n < 2; n++) {
      vcs.setTarget(mh_foo);
      for (int i = 0; i < 5; i++) {
        assertEquals(RESULT1, runVolatileCallSite());
      }
      vcs.setTarget(mh_bar);
      for (int i = 0; i < 5; i++) {
        assertEquals(RESULT2, runVolatileCallSite());
      }
    }
  }

  private static int runMutableCallSite() throws Throwable {
    int sum = 0;
    for (int i = 0; i < N; i++) {
      sum += (int) INDY_mcs().invokeExact(i, i + 1);
    }
    return sum;
  }

  private static int runVolatileCallSite() throws Throwable {
    int sum = 0;
    for (int i = 0; i < N; i++) {
      sum += (int) INDY_vcs().invokeExact(i, i + 1);
    }
    return sum;
  }

  static int foo(int a, int b) {
    return a + b;
  }

  static int bar(int a, int b) {
    return a - b;
  }

  private static MethodType MT_bsm() {
    shouldNotCallThis();
    return methodType(CallSite.class, Lookup.class, String.class, MethodType.class);
  }

  private static CallSite bsm_mcs(Lookup caller, String name, MethodType type)
      throws ReflectiveOperationException {
    return mcs;
  }

  private static MethodHandle MH_bsm_mcs() throws ReflectiveOperationException {
    shouldNotCallThis();
    return MethodHandles.lookup()
        .findStatic(MethodHandles.lookup().lookupClass(), "bsm_mcs", MT_bsm());
  }

  private static MethodHandle INDY_mcs() throws Throwable {
    shouldNotCallThis();
    return ((CallSite) MH_bsm_mcs()
        .invoke(MethodHandles.lookup(), "foo", methodType(int.class, int.class, int.class)))
        .dynamicInvoker();
  }

  private static CallSite bsm_vcs(Lookup caller, String name, MethodType type)
      throws ReflectiveOperationException {
    return vcs;
  }

  private static MethodHandle MH_bsm_vcs() throws ReflectiveOperationException {
    shouldNotCallThis();
    return MethodHandles.lookup()
        .findStatic(MethodHandles.lookup().lookupClass(), "bsm_vcs", MT_bsm());
  }

  private static MethodHandle INDY_vcs() throws Throwable {
    shouldNotCallThis();
    return ((CallSite) MH_bsm_vcs()
        .invoke(MethodHandles.lookup(), "foo", methodType(int.class, int.class, int.class)))
        .dynamicInvoker();
  }

  private static void shouldNotCallThis() {
    // if this gets called, the transformation has not taken place
    throw new AssertionError("this code should be statically transformed away by Indify");
  }
}
